
 /**
  * LICENCIA LGPL:
  * 
  * Esta librería es Software Libre; Usted puede redistribuirla y/o modificarla
  * bajo los términos de la GNU Lesser General Public License (LGPL) tal y como 
  * ha sido publicada por la Free Software Foundation; o bien la versión 2.1 de 
  * la Licencia, o (a su elección) cualquier versión posterior.
  * 
  * Esta librería se distribuye con la esperanza de que sea útil, pero SIN 
  * NINGUNA GARANTÍA; tampoco las implícitas garantías de MERCANTILIDAD o 
  * ADECUACIÓN A UN PROPÓSITO PARTICULAR. Consulte la GNU Lesser General Public 
  * License (LGPL) para más detalles
  * 
  * Usted debe recibir una copia de la GNU Lesser General Public License (LGPL) 
  * junto con esta librería; si no es así, escriba a la Free Software Foundation 
  * Inc. 51 Franklin Street, 5º Piso, Boston, MA 02110-1301, USA o consulte
  * <http://www.gnu.org/licenses/>.
  *
  * Copyright 2011 Agencia de Tecnología y Certificación Electrónica
  */
 package es.accv.arangi.base.device;
 
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.KeyStoreSpi;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.Collection;
 
 import org.apache.log4j.Logger;
 
 import es.accv.arangi.base.exception.device.OpeningDeviceException;
 import es.accv.arangi.base.exception.device.WindowsNotFoundException;
 
 /**
  * SOLO FUNCIONA CON VERSIONES DE JAVA A PARTIR DE LA 1.6.<BR><BR>
  * 
  * Clase para tratar el almacén de claves personal de Windows, es decir
  * el que contiene los certificados que no son de autoridades de certificación.<br><br>
  * 
  * Ejemplo de uso:<br><br>
  * 
  * <code>
  *  IDocument document = new FileDocument(new File ("/documento.txt"));<br>
  * 	PersonalWindowsKeyStoreManager manager = new PersonalWindowsKeyStoreManager ();<br>
  * 	String aliases = manager.getAliasNamesList();<br>
  * 	for (int i=0;i<aliases.length;i++) {<br>
  *  &nbsp;&nbsp;System.out.println ("Certificate: " + manager.getCertificate(aliases[i]));<br>
  * 	&nbsp;&nbsp;System.out.println ("Firma: " + manager.signDocument(document, aliases[i])); // Firma con la clave privada del alias<br>
  * 	}<br>
  * </code>
  * 
  * @author <a href="mailto:jgutierrez@accv.es">José M Gutiérrez</a>
  */
 public class PersonalWindowsKeyStoreManager extends AbstractBrowserKeyStoreManager {
 
 	/*
 	 * Logger de la clase
 	 */
 	Logger logger = Logger.getLogger(PersonalWindowsKeyStoreManager.class);
 	
 	//-- Constructores
 	
 	/**
 	 * Constructor: inicializa el keystore personal de Windows.
 	 * 
 	 * @throws OpeningDeviceException Error abriendo el keystore personal de Windows
 	 * @throws WindowsNotFoundException No existe el almacén de claves de Windows debido
 	 * 	a que la ejecución se produce en un sistema operativo diferente
 	 */
 	public PersonalWindowsKeyStoreManager () throws OpeningDeviceException, WindowsNotFoundException {
 		
 		if (!System.getProperty("os.name").contains("indows")) {
 			logger.info("[PersonalWindowsKeyStoreManager]:: El sistema operativo no es Windows sino " + System.getProperty("os.name"));
 			throw new WindowsNotFoundException("El sistema operativo no es Windows sino " + System.getProperty("os.name"));
 		}
 		
 		this.pin = "";
 		this.ksType = STORE_TYPE_PERSONAL_WINDOWS;
 		
 		try {
 			this.ks = KeyStore.getInstance("Windows-MY");
 			ks.load(null);
 		} catch (KeyStoreException e) {
 			logger.info("[PersonalWindowsKeyStoreManager]::No se puede obtener una instancia del keystore personal de Windows", e);
 			throw new OpeningDeviceException ("No se puede obtener una instancia del keystore personal de Windows", e);
 		} catch (NoSuchAlgorithmException e) {
 			logger.info("[PersonalWindowsKeyStoreManager]::No existe el algoritmo que comprueba la integridad del keystore personal de Windows", e);
 			throw new OpeningDeviceException ("No existe el algoritmo que comprueba la integridad del keystore personal de Windows", e);
 		} catch (CertificateException e1) {
 			logger.info("[PersonalWindowsKeyStoreManager]::Alguno de los certificados del dispositivo no puede ser cargado", e1);
 			throw new OpeningDeviceException ("Alguno de los certificados del dispositivo no puede ser cargado", e1);
 		} catch (IOException e1) {
 			logger.info("[PersonalWindowsKeyStoreManager]::Error de entrada/salida cargando el keystore personal de Windows", e1);
 			throw new OpeningDeviceException ("Error de entrada/salida cargando el keystore personal de Windows", e1);
 		}
 		
 		//-- Renombrar los alias (en IExplorer pueden estar repetidos)
 		try {
 			cleanCAPIDuplicateAliases(ks);
 		} catch (Throwable e) {
 			logger.info("[PersonalWindowsKeyStoreManager]::No se han podido renombrar los alias duplicados en el repositorio de Windows", e);
 		}
 	}
 	
 	//-- Métodos públicos
 	
 	/**
 	 * Cerrar el keystore y eliminar el provider de SUN de la lista de proveedores
 	 */
 	public void close() {
 		logger.debug ("Cerrando Keystore...");
 
 		ks = null;
 
 		logger.debug("Cerrado Keystore...OK");
 	}
 	
 	@Override
 	public void initialize() {
 		try {
 			this.ks = KeyStore.getInstance("Windows-MY");
 			ks.load(null);
 			cleanCAPIDuplicateAliases(ks);			
 		} catch (Throwable e) {
 			logger.error("[PersonalWindowsKeyStoreManager.initialize]:: Error al reinicializar el KeyStore", e);
 		}
 	}
 
 	/* (non-Javadoc)
 	 * @see es.accv.arangi.base.device.DeviceManager#isAliasFree(java.lang.String)
 	 */
 	public boolean isAliasFree(String alias){
 		
 		logger.debug ("[PersonalWindowsKeyStoreManager.isAliasFree]::Entrada::" + alias);
 		
 		boolean isFree = false;
 		try {
 			isFree = !ks.containsAlias(alias);
 		} catch (KeyStoreException e) {
 			logger.info("[PersonalWindowsKeyStoreManager.isAliasFree]::Error comprobando si existe el alias '" + alias + "'", e);
 		}
 		return isFree;
 	}
 	
 
 
 	//-- Métodos privados
 	
 	static void cleanCAPIDuplicateAliases(KeyStore keyStore) throws Throwable {
 		Field field;
 		KeyStoreSpi keyStoreVeritable;
 
 		field = keyStore.getClass().getDeclaredField("keyStoreSpi");
 		field.setAccessible(true);
 		keyStoreVeritable = (KeyStoreSpi)field.get(keyStore);
 
 		if("sun.security.mscapi.KeyStore$MY".equals(keyStoreVeritable.getClass().getName())) {
 			Collection entries;
 			String alias, hashCode;
 			X509Certificate[] certificates;
 
 			field = keyStoreVeritable.getClass().getEnclosingClass().getDeclaredField("entries");
 			field.setAccessible(true);
 			entries = (Collection)field.get(keyStoreVeritable);
 
 			for(Object entry : entries) {
 				field = entry.getClass().getDeclaredField("certChain");
 				field.setAccessible(true);
 				certificates = (X509Certificate[])field.get(entry);
 
 				hashCode = Integer.toString(certificates[0].hashCode());
 
 				field = entry.getClass().getDeclaredField("alias");
 				field.setAccessible(true);
 				alias = (String)field.get(entry);
 				
 				if(!alias.equals(hashCode)) {
 					field.set(entry, alias.concat(" - ").concat(hashCode));
 				}
 			} 
 		} 
 
 	}
 
 }